library(tidyverse)
library(plotly)

Attaching package: ‘plotly’

The following object is masked from ‘package:ggplot2’:

    last_plot

The following object is masked from ‘package:stats’:

    filter

The following object is masked from ‘package:graphics’:

    layout
load("wireless.rda")
wireless_feature = wireless[,3:7] %>%
  mutate(d_S1 = apply(wireless[,1:2], 1, function(x){sqrt(sum((x - as.numeric(AP[1,]))^2))})) %>%
  mutate(d_S2 = apply(wireless[,1:2], 1, function(x){sqrt(sum((x - as.numeric(AP[2,]))^2))})) %>%
  mutate(d_S3 = apply(wireless[,1:2], 1, function(x){sqrt(sum((x - as.numeric(AP[3,]))^2))})) %>%
  mutate(d_S4 = apply(wireless[,1:2], 1, function(x){sqrt(sum((x - as.numeric(AP[4,]))^2))})) %>%
  mutate(d_S5 = apply(wireless[,1:2], 1, function(x){sqrt(sum((x - as.numeric(AP[5,]))^2))}))
plot(x=wireless$x, y=wireless$y)
points(x=AP$x,y=AP$y, col="red", cex=1)
# exploring relationship between distance and signal strength
# without the loss of generality, use X
ap1 = as.numeric(AP[1,])
distances = apply(wireless[,1:2], 1, function(x){sqrt(sum((x - ap1)^2))})
par(mfrow=c(2,2))
plot(y=(distances[distances < cutoff])^2, x=-wireless$S1[distances < cutoff])
plot(x=log(-wireless$S1[distances < cutoff]), y=2*log(distances[distances < cutoff]))
plot(y=distances[distances < cutoff], x=-wireless$S1[distances < cutoff])
plot(x=log(-wireless$S1[distances < cutoff]), y=log(distances[distances < cutoff]))

mod = lm(wireless$S2~log(distances2))
summary(mod)

TODO: try to do that for S2, S3… as well

# exploring relationship between distance and signal strength
# without the loss of generality, use X
ap2 = as.numeric(AP[2,])
distances2 = apply(wireless[,1:2], 1, function(x){sqrt(sum((x - ap2)^2))})
par(mfrow=c(1,2))
plot(y=distances2, x=wireless$S2)
plot(x=distances2, y=-wireless$S2)

cutoff = 100
sample_size = 40
num_iter = 1000
k_s = numeric(num_iter)
r_s = numeric(num_iter)
for (i in 1:num_iter) {
  temp_indices = sample((1:254)[distances < cutoff], sample_size)
  temp_mod = lm(wireless_feature[temp_indices ,1]~distances[temp_indices])
  k_s[i] = temp_mod$coefficients[2]
  r_s[i] = summary(temp_mod)$adj.r.squared
}
mean(k_s)
[1] -0.421104
mean(r_s)
[1] 0.8751914
# using bagging to find the coefficients, using 40 points 
k_s = numeric(num_iter)
for (i in 1:num_iter) {
  temp_indices = sample((1:254)[distances2 < cutoff], sample_size)
  temp_mod = lm(wireless_feature[temp_indices ,2]~distances2[temp_indices])
  k_s[i] = temp_mod$coefficients[2]
}
mean(k_s)
[1] -0.4250205
plot(x=(distances2)[distances2 < cutoff], y=-wireless$S2[distances2 < cutoff])
#for distance above 100, the linear relationship between signal and distance breaks
#lots of points have -92 the worse signal ever 
k = -0.42
d = apply(AP, 1, function(x){sqrt(sum((x - wireless[107,1:2])^2))})
dxy = t(apply(AP, 1, function(x) {as.numeric(wireless[107,1:2]) - x}))
ds.dxy = apply(dxy, 2, function(x) as.numeric(k * x / d))
ds.dxy
              x           y
[1,] -0.1784933 -0.38018434
[2,]  0.3837247 -0.17074941
[3,]  0.4188134  0.03154903
[4,] -0.4035757  0.11630423
[5,]  0.3511401 -0.23043571
ds = wireless_feature[86,] - wireless_feature[107,] 
k = mod1$coefficients[2]
b = mod1$coefficients[1]
signals = wireless_feature[224,]
test = t(apply(AP, 1, function(x) {as.numeric(wireless[224,1:2]) - x}))
test2 = apply(test, 2, function(x) as.numeric(k^2/(signals - b)) * x)
df_mod = lm(as.numeric(wireless_feature[223,] - wireless_feature[224,])~0 + 
                         test2[,1] + test2[,2])
summary(df_mod)

Call:
lm(formula = as.numeric(wireless_feature[223, ] - wireless_feature[224, 
    ]) ~ 0 + test2[, 1] + test2[, 2])

Residuals:
      1       2       3       4       5 
 0.2441  5.0087  1.8471 -0.5603  2.8118 

Coefficients:
           Estimate Std. Error t value Pr(>|t|)
test2[, 1]   -1.643      2.177  -0.755    0.505
test2[, 2]   -2.593      5.225  -0.496    0.654

Residual standard error: 3.501 on 3 degrees of freedom
Multiple R-squared:  0.1827,    Adjusted R-squared:  -0.3622 
F-statistic: 0.3353 on 2 and 3 DF,  p-value: 0.7389

##exploring using differentials 
##

sample_index = sample(1:nrow(wireless), 1)
sample_point = wireless[sample_index,]
sample_diff = data.frame(t(apply(wireless[-sample_index,], 1, function(x) x - as.numeric(wireless[sample_index,]))))
sample_diff_y = sample_diff[sample_diff$y == 0,]

mod = lm(x~.-y, data=sample_diff_y)
summary(mod)
basic_x = lm(x~.-y, data=wireless)
basic_y = lm(y~.-x, data=wireless)

#summary(basic_x)
#summary(basic_y)

avg_error = mean(sqrt((wireless$x - basic_x$fitted.values)^2 + (wireless$y - basic_y$fitted.values)^2))
avg_error
plot(wireless$x, wireless$y)
points(basic_x$fitted.values, basic_y$fitted.values, col="red")
segments(wireless$x, wireless$y, basic_x$fitted.values, basic_y$fitted.values, col="blue")
n = nrow(wireless)
#train_percent = 0.6
#sample_indices = sample(1:nrow(wireless), train_percent*n)
knn_predictions = numeric(n)
pwdistances = as.matrix(dist(wireless[,3:7]))
for (i in 1:n) {
  knn_predictions[i] = (1:n)[-i][which.min(as.matrix(pdist::pdist(1/wireless[,(3:7)][i,], 1/wireless[,(3:7)][-i,])))]
}
knn_x = wireless$x[knn_predictions]
knn_y = wireless$y[knn_predictions]
par(mfrow=c(1,2))
plot(density(sqrt((wireless$x - knn_x)^2 + (wireless$y - knn_y)^2)),
     main = "knn performance")

#plot(density(sqrt((wireless$x - basic_x$fitted.values)^2 + (wireless$y - basic_y$fitted.values)^2)),
#     main = "regression performance")
knn_errors = sqrt((wireless$x - knn_x)^2 + (wireless$y - knn_y)^2)
knn_avg_error = mean(knn_errors[knn_errors < 100])
knn_avg_error
[1] 11.845
plot(wireless$x, wireless$y)
points(knn_x, knn_y, col="red")
segments(wireless$x, wireless$y, knn_x, knn_y, col="blue")

knn_bad_loc = wireless[knn_errors > 20,] %>%
  mutate(error = knn_errors[knn_errors > 20]) %>%
  mutate(index = (1:254)[knn_errors > 20]) %>%
  dplyr::arrange(desc(error))
hover_text = apply(wireless_feature[,1:5],1, function(x) paste(x,collapse = "|"))
p = plot_ly(wireless, x=~x, y=~y, name = "receivers", type="scatter", 
            mode="markers", text=paste(1:254, "<br>", hover_text)) %>% 
  add_trace(x=AP$x, y=AP$y, name = "wifi post", mode="markers", text=rownames(AP)) %>%
  add_trace(x=wireless$x[knn_errors > 20], y=wireless$y[knn_errors > 20], name = "badloc", mode="markers", text=hover_text[knn_errors > 20]) 
p
#trying nearest neightbor + trigulation
point_index = 4
point = wireless[point_index,3:7]
point_d = pdist::pdist(point, wireless[-point_index,3:7])@dist
top_three = (1:254)[-point_index][order(point_d)[1:3]]
top_three
[1] 156 157 126
hover_text = apply(wireless_feature[,1:5],1, function(x) paste(x,collapse = "|"))
p = plot_ly(wireless, x=~x, y=~y, name = "receivers", type="scatter", 
            mode="markers", text=paste(1:254, "<br>", hover_text)) %>% 
  add_trace(x=AP$x, y=AP$y, name = "wifi post", mode="markers", text=rownames(AP)) %>%
  add_trace(x=wireless$x[top_three], y=wireless$y[top_three], 
            name = "neighbors", mode="markers", 
            text=paste(top_three, "<br>", hover_text[top_three])) %>%
  add_trace(x=wireless$x[point_index], y=wireless$y[point_index], 
            name = "neighbors", mode="markers", 
            text=paste(point_index, "<br>", hover_text[point_index]))
p
point_index = 113
point = wireless[point_index,3:7]
point_d = pdist::pdist(point, wireless[-point_index,(3:7)])@dist
top_three = (1:254)[-point_index][order(point_d)[1:3]]
neighbors = numeric(5)
for (i in 1:5) {
  neighbors[i] = (1:n)[-point_index][which.min(as.matrix(pdist::pdist(wireless[,(3:7)[-i]][point_index,], wireless[,(3:7)[-i]][-point_index,])))]
}
neighbors
[1]  33 153   9  33   4
dist(wireless[neighbors,1:2])
           33      153        9     33.1
153  38.47077                           
9    54.00000 79.62412                  
33.1  0.00000 38.47077 54.00000         
4    30.30000 59.06683 23.70000 30.30000

5

point
hover_text = apply(wireless_feature[,1:5],1, function(x) paste(x,collapse = "|"))
p = plot_ly(wireless, x=~x, y=~y, name = "receivers", type="scatter", 
            mode="markers", text=paste(1:254, "<br>", hover_text)) %>% 
  add_trace(x=AP$x, y=AP$y, name = "wifi post", mode="markers", text=rownames(AP)) %>%
  add_trace(x=wireless$x[neighbors], y=wireless$y[neighbors], 
            name = "neighbors", mode="markers", 
            text=paste(neighbors, "<br>", hover_text[neighbors])) #%>%
  #add_trace(x=wireless$x[point_index], y=wireless$y[point_index], 
  #          name = "point", mode="markers", 
  #          text=paste(point_index, "<br>", hover_text[point_index]))
  
p
hover_text = apply(wireless_feature[,1:5],1, function(x) paste(x,collapse = "|"))
p = plot_ly(wireless, x=~x, y=~y, name = "receivers", type="scatter", 
            mode="markers", text=paste(1:254, "<br>", hover_text)) %>% 
  add_trace(x=AP$x, y=AP$y, name = "wifi post", mode="markers", text=rownames(AP)) %>%
  add_trace(x=wireless$x[top_three], y=wireless$y[top_three], 
            name = "neighbors", mode="markers", 
            text=paste(top_three, "<br>", hover_text[top_three])) %>%
  add_trace(x=wireless$x[point_index], y=wireless$y[point_index], 
            name = "point", mode="markers", 
            text=paste(point_index, "<br>", hover_text[point_index]))
  
p
knn_predictions[3]
[1] 37
neighbors = numeric(5)
for (i in 1:5) {
  neighbors[i] = (1:n)[-point_index][which.min(as.matrix(pdist::pdist(1/wireless[,(3:7)[-i]][point_index,], 1/wireless[,(3:7)[-i]][-point_index,])))]
}
neighbors
[1] 244 244 244 244 233

after analyzing the error, I find that point 243’s signal for AP3 is completely bad comparing to its neighbors, lets check other access points.

Some points, they only f* up on signal from an access point.

set.seed(12345)
sample_indices = sample(1:254, 50)
plot(wireless$x[sample_indices], wireless$y[sample_indices], ylim=c(0, 145), xlim=c(10, 235))
points(knn_x[sample_indices], knn_y[sample_indices], col="yellow")
points((knn_x-df_x)[sample_indices], (knn_y-df_y)[sample_indices], col="red")

segments(wireless$x[sample_indices], wireless$y[sample_indices], knn_x[sample_indices], knn_y[sample_indices], col="blue")
segments(knn_x[sample_indices], knn_y[sample_indices], (knn_x-df_x)[sample_indices], (knn_y-df_y)[sample_indices], col="green")
#segments(wireless$x, wireless$y, knn_x+df_x, knn_y+ df_y, col="green")
par(mfrow=c(1,2))
plot(wireless_feature$S1, wireless_feature$d1, main="ap1")
plot(wireless_feature$S2, wireless_feature$d2, main="ap2")

plot(wireless_feature$S3, wireless_feature$d3, main="ap3")
plot(wireless_feature$S4, wireless_feature$d4, main="ap4")

plot(wireless_feature$S5, wireless_feature$d5, main="ap5")

From the first half of the graph, we can see for different AP, the variance spikes at different points.

Looking at the second half of the graphs.
For access point 5, the relationship between distance and signal is very weak, while
others are more stable. This may have to do with AP5 is in the center of the building.

hover_text = apply(wireless_feature,1, function(x) paste(x,collapse = "|"))
p = plot_ly(wireless, x=~x, y=~y, name = "receivers", type="scatter", 
            mode="markers", text=paste(1:254, "<br>", hover_text)) %>% 
  add_trace(x=AP$x, y=AP$y, name = "wifi post", mode="markers", text=rownames(AP))
p
kclusters = kmeans(wireless[,3:7], 5)
#kclusters$cluster

ggplot(data=wireless) +
  geom_point(aes(x=x,y=y), colour=kclusters$cluster) 
ggplot(data=wireless) +
  geom_point(aes(x=x,y=y)) +
  scale_fill_manual(kclusters$cluster)
cutoff = 68
# seems like 70 is a good cut off lets check how many points have more than 70
wireless_strong = wireless %>%
  mutate(S1 = S1 > -cutoff) %>% 
  mutate(S2 = S2 > -cutoff) %>% 
  mutate(S3 = S3 > -cutoff) %>% 
  mutate(S4 = S4 > -cutoff) %>% 
  mutate(S5 = S5 > -cutoff) 
# seems like 70 is not a good cutoff as we think
table(apply(wireless_strong[,3:7], 1, sum))

  0   1   2   3 
 19 150  80   5 
bad_locations = wireless_strong[as.numeric(apply(wireless_strong[,3:7], 1, sum)) < 2,]

plot(x=bad_locations$x, y=bad_locations$y, ylim=c(0,150), xlim=c(0,230))
points(x=AP$x,y=AP$y, col="red", cex=5)
View(data.frame(table(wireless$y)))
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3IgbGlicmFyeX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkocGxvdGx5KQpgYGAKCgpgYGB7ciBpbXBvcnRfZGF0YX0KbG9hZCgid2lyZWxlc3MucmRhIikKd2lyZWxlc3NfZmVhdHVyZSA9IHdpcmVsZXNzWywzOjddICU+JQogIG11dGF0ZShkMSA9IGFwcGx5KHdpcmVsZXNzWywxOjJdLCAxLCBmdW5jdGlvbih4KXtzcXJ0KHN1bSgoeCAtIGFzLm51bWVyaWMoQVBbMSxdKSleMikpfSkpICU+JQogIG11dGF0ZShkMiA9IGFwcGx5KHdpcmVsZXNzWywxOjJdLCAxLCBmdW5jdGlvbih4KXtzcXJ0KHN1bSgoeCAtIGFzLm51bWVyaWMoQVBbMixdKSleMikpfSkpICU+JQogIG11dGF0ZShkMyA9IGFwcGx5KHdpcmVsZXNzWywxOjJdLCAxLCBmdW5jdGlvbih4KXtzcXJ0KHN1bSgoeCAtIGFzLm51bWVyaWMoQVBbMyxdKSleMikpfSkpICU+JQogIG11dGF0ZShkNCA9IGFwcGx5KHdpcmVsZXNzWywxOjJdLCAxLCBmdW5jdGlvbih4KXtzcXJ0KHN1bSgoeCAtIGFzLm51bWVyaWMoQVBbNCxdKSleMikpfSkpICU+JQogIG11dGF0ZShkNSA9IGFwcGx5KHdpcmVsZXNzWywxOjJdLCAxLCBmdW5jdGlvbih4KXtzcXJ0KHN1bSgoeCAtIGFzLm51bWVyaWMoQVBbNSxdKSleMikpfSkpCmBgYAoKYGBge3Igc2VlaW5nX3BsYWNlc30KcGxvdCh4PXdpcmVsZXNzJHgsIHk9d2lyZWxlc3MkeSkKcG9pbnRzKHg9QVAkeCx5PUFQJHksIGNvbD0icmVkIiwgY2V4PTEpCmBgYAoKYGBge3J9CiMgZXhwbG9yaW5nIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGRpc3RhbmNlIGFuZCBzaWduYWwgc3RyZW5ndGgKIyB3aXRob3V0IHRoZSBsb3NzIG9mIGdlbmVyYWxpdHksIHVzZSBYCmFwMSA9IGFzLm51bWVyaWMoQVBbMSxdKQpkaXN0YW5jZXMgPSBhcHBseSh3aXJlbGVzc1ssMToyXSwgMSwgZnVuY3Rpb24oeCl7c3FydChzdW0oKHggLSBhcDEpXjIpKX0pCgpwYXIobWZyb3c9YygyLDIpKQpwbG90KHk9KGRpc3RhbmNlc1tkaXN0YW5jZXMgPCBjdXRvZmZdKV4yLCB4PS13aXJlbGVzcyRTMVtkaXN0YW5jZXMgPCBjdXRvZmZdKQoKcGxvdCh4PWxvZygtd2lyZWxlc3MkUzFbZGlzdGFuY2VzIDwgY3V0b2ZmXSksIHk9Mipsb2coZGlzdGFuY2VzW2Rpc3RhbmNlcyA8IGN1dG9mZl0pKQoKcGxvdCh5PWRpc3RhbmNlc1tkaXN0YW5jZXMgPCBjdXRvZmZdLCB4PS13aXJlbGVzcyRTMVtkaXN0YW5jZXMgPCBjdXRvZmZdKQoKcGxvdCh4PWxvZygtd2lyZWxlc3MkUzFbZGlzdGFuY2VzIDwgY3V0b2ZmXSksIHk9bG9nKGRpc3RhbmNlc1tkaXN0YW5jZXMgPCBjdXRvZmZdKSkKYGBgCgoKYGBge3J9Cm1vZCA9IGxtKHdpcmVsZXNzJFMyfmxvZyhkaXN0YW5jZXMyKSkKc3VtbWFyeShtb2QpCmBgYAoKVE9ETzoKdHJ5IHRvIGRvIHRoYXQgZm9yIFMyLCBTMy4uLiBhcyB3ZWxsCgpgYGB7cn0KIyBleHBsb3JpbmcgcmVsYXRpb25zaGlwIGJldHdlZW4gZGlzdGFuY2UgYW5kIHNpZ25hbCBzdHJlbmd0aAojIHdpdGhvdXQgdGhlIGxvc3Mgb2YgZ2VuZXJhbGl0eSwgdXNlIFgKYXAyID0gYXMubnVtZXJpYyhBUFsyLF0pCmRpc3RhbmNlczIgPSBhcHBseSh3aXJlbGVzc1ssMToyXSwgMSwgZnVuY3Rpb24oeCl7c3FydChzdW0oKHggLSBhcDIpXjIpKX0pCgpwYXIobWZyb3c9YygxLDIpKQoKcGxvdCh5PWRpc3RhbmNlczIsIHg9d2lyZWxlc3MkUzIpCnBsb3QoeD1kaXN0YW5jZXMyLCB5PS13aXJlbGVzcyRTMikKCnBsb3QoeT1kaXN0YW5jZXMsIHg9d2lyZWxlc3MkUzEpCnBsb3QoeD1kaXN0YW5jZXMsIHk9LXdpcmVsZXNzJFMxKQpgYGAKCmBgYHtyIGJhZ19pbml0fQpjdXRvZmYgPSAxMDAKc2FtcGxlX3NpemUgPSA0MApudW1faXRlciA9IDEwMDAKYGBgCgoKYGBge3IgYmFnMX0Ka19zID0gbnVtZXJpYyhudW1faXRlcikKcl9zID0gbnVtZXJpYyhudW1faXRlcikKCmZvciAoaSBpbiAxOm51bV9pdGVyKSB7CiAgdGVtcF9pbmRpY2VzID0gc2FtcGxlKCgxOjI1NClbZGlzdGFuY2VzIDwgY3V0b2ZmXSwgc2FtcGxlX3NpemUpCiAgdGVtcF9tb2QgPSBsbSh3aXJlbGVzc19mZWF0dXJlW3RlbXBfaW5kaWNlcyAsMV1+ZGlzdGFuY2VzW3RlbXBfaW5kaWNlc10pCiAga19zW2ldID0gdGVtcF9tb2QkY29lZmZpY2llbnRzWzJdCiAgcl9zW2ldID0gc3VtbWFyeSh0ZW1wX21vZCkkYWRqLnIuc3F1YXJlZAp9CgptZWFuKGtfcykKbWVhbihyX3MpCmBgYAoKYGBge3IgYmFnMn0KIyB1c2luZyBiYWdnaW5nIHRvIGZpbmQgdGhlIGNvZWZmaWNpZW50cywgdXNpbmcgNDAgcG9pbnRzIAprX3MgPSBudW1lcmljKG51bV9pdGVyKQpmb3IgKGkgaW4gMTpudW1faXRlcikgewogIHRlbXBfaW5kaWNlcyA9IHNhbXBsZSgoMToyNTQpW2Rpc3RhbmNlczIgPCBjdXRvZmZdLCBzYW1wbGVfc2l6ZSkKICB0ZW1wX21vZCA9IGxtKHdpcmVsZXNzX2ZlYXR1cmVbdGVtcF9pbmRpY2VzICwyXX5kaXN0YW5jZXMyW3RlbXBfaW5kaWNlc10pCiAga19zW2ldID0gdGVtcF9tb2QkY29lZmZpY2llbnRzWzJdCn0KCm1lYW4oa19zKQpgYGAKCmBgYHtyIGJhZzV9CgpgYGAKCgoKYGBge3J9CnBsb3QoeD0oZGlzdGFuY2VzMilbZGlzdGFuY2VzMiA8IGN1dG9mZl0sIHk9LXdpcmVsZXNzJFMyW2Rpc3RhbmNlczIgPCBjdXRvZmZdKQojZm9yIGRpc3RhbmNlIGFib3ZlIDEwMCwgdGhlIGxpbmVhciByZWxhdGlvbnNoaXAgYmV0d2VlbiBzaWduYWwgYW5kIGRpc3RhbmNlIGJyZWFrcwojbG90cyBvZiBwb2ludHMgaGF2ZSAtOTIgdGhlIHdvcnNlIHNpZ25hbCBldmVyIApgYGAKCmBgYHtyfQptb2QyID0gbG0od2lyZWxlc3MkUzJbZGlzdGFuY2VzMiA8IGN1dG9mZl1+ZGlzdGFuY2VzMltkaXN0YW5jZXMyIDwgY3V0b2ZmXSkKbW9kMSA9IGxtKHdpcmVsZXNzJFMxW2Rpc3RhbmNlcyA8IGN1dG9mZl1+ZGlzdGFuY2VzW2Rpc3RhbmNlcyA8IGN1dG9mZl0pCgp3aXJlbGVzc1tjKDEwNywgODYpLF0KYGBgCgpgYGB7cn0KayA9IC0wLjQyCgpkID0gYXBwbHkoQVAsIDEsIGZ1bmN0aW9uKHgpe3NxcnQoc3VtKCh4IC0gd2lyZWxlc3NbMTA3LDE6Ml0pXjIpKX0pCgpkeHkgPSB0KGFwcGx5KEFQLCAxLCBmdW5jdGlvbih4KSB7YXMubnVtZXJpYyh3aXJlbGVzc1sxMDcsMToyXSkgLSB4fSkpCgpkcy5keHkgPSBhcHBseShkeHksIDIsIGZ1bmN0aW9uKHgpIGFzLm51bWVyaWMoayAqIHggLyBkKSkKCmRzLmR4eQoKZHMgPSB3aXJlbGVzc19mZWF0dXJlWzg2LF0gLSB3aXJlbGVzc19mZWF0dXJlWzEwNyxdIApgYGAKCgpgYGB7cn0KayA9IG1vZDEkY29lZmZpY2llbnRzWzJdCmIgPSBtb2QxJGNvZWZmaWNpZW50c1sxXQoKc2lnbmFscyA9IHdpcmVsZXNzX2ZlYXR1cmVbMjI0LF0KCnRlc3QgPSB0KGFwcGx5KEFQLCAxLCBmdW5jdGlvbih4KSB7YXMubnVtZXJpYyh3aXJlbGVzc1syMjQsMToyXSkgLSB4fSkpCgp0ZXN0MiA9IGFwcGx5KHRlc3QsIDIsIGZ1bmN0aW9uKHgpIGFzLm51bWVyaWMoa14yLyhzaWduYWxzIC0gYikpICogeCkKCmRmX21vZCA9IGxtKGFzLm51bWVyaWMod2lyZWxlc3NfZmVhdHVyZVsyMjMsXSAtIHdpcmVsZXNzX2ZlYXR1cmVbMjI0LF0pfjAgKyAKICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QyWywxXSArIHRlc3QyWywyXSkKCnN1bW1hcnkoZGZfbW9kKQoKCmBgYAoKYGBge3J9CiMxMi8zMS8yMDE3IGRpZmZlcmVudGlhbCBhdHRlbnRzCmRpZmZfMTIgPSAod2lyZWxlc3NfZmVhdHVyZSRTMSAtIHdpcmVsZXNzX2ZlYXR1cmUkUzIpCmRpZmZfMzIgPSAod2lyZWxlc3NfZmVhdHVyZSRTNCAtIHdpcmVsZXNzX2ZlYXR1cmUkUzUpCnN1bW1hcnkobG0obG9nKHdpcmVsZXNzX2ZlYXR1cmUkZDEvd2lyZWxlc3NfZmVhdHVyZSRkMil+MCArIGRpZmZfMTIpKQoKc3VtbWFyeShsbShsb2cod2lyZWxlc3NfZmVhdHVyZSRkNC93aXJlbGVzc19mZWF0dXJlJGQ1KX4wICsgZGlmZl8zMikpCgpwbG90KCh3aXJlbGVzc19mZWF0dXJlJFMxIC0gd2lyZWxlc3NfZmVhdHVyZSRTMiksIGxvZyh3aXJlbGVzc19mZWF0dXJlJGQxL3dpcmVsZXNzX2ZlYXR1cmUkZDIpKQpgYGAKCgpgYGB7cn0KIyNleHBsb3JpbmcgdXNpbmcgZGlmZmVyZW50aWFscyAKIyMKCnNhbXBsZV9pbmRleCA9IHNhbXBsZSgxOm5yb3cod2lyZWxlc3MpLCAxKQpzYW1wbGVfcG9pbnQgPSB3aXJlbGVzc1tzYW1wbGVfaW5kZXgsXQpzYW1wbGVfZGlmZiA9IGRhdGEuZnJhbWUodChhcHBseSh3aXJlbGVzc1stc2FtcGxlX2luZGV4LF0sIDEsIGZ1bmN0aW9uKHgpIHggLSBhcy5udW1lcmljKHdpcmVsZXNzW3NhbXBsZV9pbmRleCxdKSkpKQpzYW1wbGVfZGlmZl95ID0gc2FtcGxlX2RpZmZbc2FtcGxlX2RpZmYkeSA9PSAwLF0KCm1vZCA9IGxtKHh+Li15LCBkYXRhPXNhbXBsZV9kaWZmX3kpCnN1bW1hcnkobW9kKQpgYGAKCmBgYHtyfQpiYXNpY194ID0gbG0oeH4uLXksIGRhdGE9d2lyZWxlc3MpCmJhc2ljX3kgPSBsbSh5fi4teCwgZGF0YT13aXJlbGVzcykKCiNzdW1tYXJ5KGJhc2ljX3gpCiNzdW1tYXJ5KGJhc2ljX3kpCgphdmdfZXJyb3IgPSBtZWFuKHNxcnQoKHdpcmVsZXNzJHggLSBiYXNpY194JGZpdHRlZC52YWx1ZXMpXjIgKyAod2lyZWxlc3MkeSAtIGJhc2ljX3kkZml0dGVkLnZhbHVlcyleMikpCmF2Z19lcnJvcgpgYGAKCmBgYHtyIGJhc2ljX3ByZWRpY3Rpb259CnBsb3Qod2lyZWxlc3MkeCwgd2lyZWxlc3MkeSkKcG9pbnRzKGJhc2ljX3gkZml0dGVkLnZhbHVlcywgYmFzaWNfeSRmaXR0ZWQudmFsdWVzLCBjb2w9InJlZCIpCnNlZ21lbnRzKHdpcmVsZXNzJHgsIHdpcmVsZXNzJHksIGJhc2ljX3gkZml0dGVkLnZhbHVlcywgYmFzaWNfeSRmaXR0ZWQudmFsdWVzLCBjb2w9ImJsdWUiKQpgYGAKCmBgYHtyIGJhc2ljX2tubn0KbiA9IG5yb3cod2lyZWxlc3MpCiN0cmFpbl9wZXJjZW50ID0gMC42CiNzYW1wbGVfaW5kaWNlcyA9IHNhbXBsZSgxOm5yb3cod2lyZWxlc3MpLCB0cmFpbl9wZXJjZW50Km4pCmtubl9wcmVkaWN0aW9ucyA9IG51bWVyaWMobikKCnB3ZGlzdGFuY2VzID0gYXMubWF0cml4KGRpc3Qod2lyZWxlc3NbLDM6N10pKQoKZm9yIChpIGluIDE6bikgewogIGtubl9wcmVkaWN0aW9uc1tpXSA9ICgxOm4pWy1pXVt3aGljaC5taW4oYXMubWF0cml4KHBkaXN0OjpwZGlzdCgxL3dpcmVsZXNzWywoMzo3KV1baSxdLCAxL3dpcmVsZXNzWywoMzo3KV1bLWksXSkpKV0KfQpgYGAKCgpgYGB7cn0Ka25uX3ggPSB3aXJlbGVzcyR4W2tubl9wcmVkaWN0aW9uc10Ka25uX3kgPSB3aXJlbGVzcyR5W2tubl9wcmVkaWN0aW9uc10KCnBhcihtZnJvdz1jKDEsMikpCnBsb3QoZGVuc2l0eShzcXJ0KCh3aXJlbGVzcyR4IC0ga25uX3gpXjIgKyAod2lyZWxlc3MkeSAtIGtubl95KV4yKSksCiAgICAgbWFpbiA9ICJrbm4gcGVyZm9ybWFuY2UiKQojcGxvdChkZW5zaXR5KHNxcnQoKHdpcmVsZXNzJHggLSBiYXNpY194JGZpdHRlZC52YWx1ZXMpXjIgKyAod2lyZWxlc3MkeSAtIGJhc2ljX3kkZml0dGVkLnZhbHVlcyleMikpLAojICAgICBtYWluID0gInJlZ3Jlc3Npb24gcGVyZm9ybWFuY2UiKQoKa25uX2Vycm9ycyA9IHNxcnQoKHdpcmVsZXNzJHggLSBrbm5feCleMiArICh3aXJlbGVzcyR5IC0ga25uX3kpXjIpCmtubl9hdmdfZXJyb3IgPSBtZWFuKGtubl9lcnJvcnNba25uX2Vycm9ycyA8IDEwMF0pCmtubl9hdmdfZXJyb3IKYGBgCgoKYGBge3J9CnBsb3Qod2lyZWxlc3MkeCwgd2lyZWxlc3MkeSkKcG9pbnRzKGtubl94LCBrbm5feSwgY29sPSJyZWQiKQpzZWdtZW50cyh3aXJlbGVzcyR4LCB3aXJlbGVzcyR5LCBrbm5feCwga25uX3ksIGNvbD0iYmx1ZSIpCmBgYAoKYGBge3Iga25uX2Vycm9yX2FuYWx5c2lzfQprbm5fYmFkX2xvYyA9IHdpcmVsZXNzW2tubl9lcnJvcnMgPiAyMCxdICU+JQogIG11dGF0ZShlcnJvciA9IGtubl9lcnJvcnNba25uX2Vycm9ycyA+IDIwXSkgJT4lCiAgbXV0YXRlKGluZGV4ID0gKDE6MjU0KVtrbm5fZXJyb3JzID4gMjBdKSAlPiUKICBkcGx5cjo6YXJyYW5nZShkZXNjKGVycm9yKSkKYGBgCgpgYGB7cn0KaG92ZXJfdGV4dCA9IGFwcGx5KHdpcmVsZXNzX2ZlYXR1cmVbLDE6NV0sMSwgZnVuY3Rpb24oeCkgcGFzdGUoeCxjb2xsYXBzZSA9ICJ8IikpCnAgPSBwbG90X2x5KHdpcmVsZXNzLCB4PX54LCB5PX55LCBuYW1lID0gInJlY2VpdmVycyIsIHR5cGU9InNjYXR0ZXIiLCAKICAgICAgICAgICAgbW9kZT0ibWFya2VycyIsIHRleHQ9cGFzdGUoMToyNTQsICI8YnI+IiwgaG92ZXJfdGV4dCkpICU+JSAKICBhZGRfdHJhY2UoeD1BUCR4LCB5PUFQJHksIG5hbWUgPSAid2lmaSBwb3N0IiwgbW9kZT0ibWFya2VycyIsIHRleHQ9cm93bmFtZXMoQVApKSAlPiUKICBhZGRfdHJhY2UoeD13aXJlbGVzcyR4W2tubl9lcnJvcnMgPiAyMF0sIHk9d2lyZWxlc3MkeVtrbm5fZXJyb3JzID4gMjBdLCBuYW1lID0gImJhZGxvYyIsIG1vZGU9Im1hcmtlcnMiLCB0ZXh0PWhvdmVyX3RleHRba25uX2Vycm9ycyA+IDIwXSkgCnAKYGBgCgpgYGB7cn0KI3RyeWluZyBuZWFyZXN0IG5laWdodGJvciArIHRyaWd1bGF0aW9uCnBvaW50X2luZGV4ID0gNApwb2ludCA9IHdpcmVsZXNzW3BvaW50X2luZGV4LDM6N10KcG9pbnRfZCA9IHBkaXN0OjpwZGlzdChwb2ludCwgd2lyZWxlc3NbLXBvaW50X2luZGV4LDM6N10pQGRpc3QKdG9wX3RocmVlID0gKDE6MjU0KVstcG9pbnRfaW5kZXhdW29yZGVyKHBvaW50X2QpWzE6M11dCnRvcF90aHJlZQpgYGAKCmBgYHtyIHZpc3VhbGl6aW5nfQpob3Zlcl90ZXh0ID0gYXBwbHkod2lyZWxlc3NfZmVhdHVyZVssMTo1XSwxLCBmdW5jdGlvbih4KSBwYXN0ZSh4LGNvbGxhcHNlID0gInwiKSkKcCA9IHBsb3RfbHkod2lyZWxlc3MsIHg9fngsIHk9fnksIG5hbWUgPSAicmVjZWl2ZXJzIiwgdHlwZT0ic2NhdHRlciIsIAogICAgICAgICAgICBtb2RlPSJtYXJrZXJzIiwgdGV4dD1wYXN0ZSgxOjI1NCwgIjxicj4iLCBob3Zlcl90ZXh0KSkgJT4lIAogIGFkZF90cmFjZSh4PUFQJHgsIHk9QVAkeSwgbmFtZSA9ICJ3aWZpIHBvc3QiLCBtb2RlPSJtYXJrZXJzIiwgdGV4dD1yb3duYW1lcyhBUCkpICU+JQogIGFkZF90cmFjZSh4PXdpcmVsZXNzJHhbdG9wX3RocmVlXSwgeT13aXJlbGVzcyR5W3RvcF90aHJlZV0sIAogICAgICAgICAgICBuYW1lID0gIm5laWdoYm9ycyIsIG1vZGU9Im1hcmtlcnMiLCAKICAgICAgICAgICAgdGV4dD1wYXN0ZSh0b3BfdGhyZWUsICI8YnI+IiwgaG92ZXJfdGV4dFt0b3BfdGhyZWVdKSkgJT4lCiAgYWRkX3RyYWNlKHg9d2lyZWxlc3MkeFtwb2ludF9pbmRleF0sIHk9d2lyZWxlc3MkeVtwb2ludF9pbmRleF0sIAogICAgICAgICAgICBuYW1lID0gIm5laWdoYm9ycyIsIG1vZGU9Im1hcmtlcnMiLCAKICAgICAgICAgICAgdGV4dD1wYXN0ZShwb2ludF9pbmRleCwgIjxicj4iLCBob3Zlcl90ZXh0W3BvaW50X2luZGV4XSkpCnAKYGBgCgpgYGB7cn0KcG9pbnRfaW5kZXggPSAxMTMKcG9pbnQgPSB3aXJlbGVzc1twb2ludF9pbmRleCwzOjddCnBvaW50X2QgPSBwZGlzdDo6cGRpc3QocG9pbnQsIHdpcmVsZXNzWy1wb2ludF9pbmRleCwoMzo3KV0pQGRpc3QKdG9wX3RocmVlID0gKDE6MjU0KVstcG9pbnRfaW5kZXhdW29yZGVyKHBvaW50X2QpWzE6M11dCgoKbmVpZ2hib3JzID0gbnVtZXJpYyg1KQpmb3IgKGkgaW4gMTo1KSB7CiAgbmVpZ2hib3JzW2ldID0gKDE6bilbLXBvaW50X2luZGV4XVt3aGljaC5taW4oYXMubWF0cml4KHBkaXN0OjpwZGlzdCh3aXJlbGVzc1ssKDM6NylbLWldXVtwb2ludF9pbmRleCxdLCB3aXJlbGVzc1ssKDM6NylbLWldXVstcG9pbnRfaW5kZXgsXSkpKV0KfQoKbmVpZ2hib3JzCmRpc3Qod2lyZWxlc3NbbmVpZ2hib3JzLDE6Ml0pCmBgYAo1CmBgYHtyfQpwb2ludApgYGAKCgpgYGB7cn0KaG92ZXJfdGV4dCA9IGFwcGx5KHdpcmVsZXNzX2ZlYXR1cmVbLDE6NV0sMSwgZnVuY3Rpb24oeCkgcGFzdGUoeCxjb2xsYXBzZSA9ICJ8IikpCnAgPSBwbG90X2x5KHdpcmVsZXNzLCB4PX54LCB5PX55LCBuYW1lID0gInJlY2VpdmVycyIsIHR5cGU9InNjYXR0ZXIiLCAKICAgICAgICAgICAgbW9kZT0ibWFya2VycyIsIHRleHQ9cGFzdGUoMToyNTQsICI8YnI+IiwgaG92ZXJfdGV4dCkpICU+JSAKICBhZGRfdHJhY2UoeD1BUCR4LCB5PUFQJHksIG5hbWUgPSAid2lmaSBwb3N0IiwgbW9kZT0ibWFya2VycyIsIHRleHQ9cm93bmFtZXMoQVApKSAlPiUKICBhZGRfdHJhY2UoeD13aXJlbGVzcyR4W25laWdoYm9yc10sIHk9d2lyZWxlc3MkeVtuZWlnaGJvcnNdLCAKICAgICAgICAgICAgbmFtZSA9ICJuZWlnaGJvcnMiLCBtb2RlPSJtYXJrZXJzIiwgCiAgICAgICAgICAgIHRleHQ9cGFzdGUobmVpZ2hib3JzLCAiPGJyPiIsIGhvdmVyX3RleHRbbmVpZ2hib3JzXSkpICMlPiUKICAjYWRkX3RyYWNlKHg9d2lyZWxlc3MkeFtwb2ludF9pbmRleF0sIHk9d2lyZWxlc3MkeVtwb2ludF9pbmRleF0sIAogICMgICAgICAgICAgbmFtZSA9ICJwb2ludCIsIG1vZGU9Im1hcmtlcnMiLCAKICAjICAgICAgICAgIHRleHQ9cGFzdGUocG9pbnRfaW5kZXgsICI8YnI+IiwgaG92ZXJfdGV4dFtwb2ludF9pbmRleF0pKQogIApwCmBgYAoKYGBge3J9CmhvdmVyX3RleHQgPSBhcHBseSh3aXJlbGVzc19mZWF0dXJlWywxOjVdLDEsIGZ1bmN0aW9uKHgpIHBhc3RlKHgsY29sbGFwc2UgPSAifCIpKQpwID0gcGxvdF9seSh3aXJlbGVzcywgeD1+eCwgeT1+eSwgbmFtZSA9ICJyZWNlaXZlcnMiLCB0eXBlPSJzY2F0dGVyIiwgCiAgICAgICAgICAgIG1vZGU9Im1hcmtlcnMiLCB0ZXh0PXBhc3RlKDE6MjU0LCAiPGJyPiIsIGhvdmVyX3RleHQpKSAlPiUgCiAgYWRkX3RyYWNlKHg9QVAkeCwgeT1BUCR5LCBuYW1lID0gIndpZmkgcG9zdCIsIG1vZGU9Im1hcmtlcnMiLCB0ZXh0PXJvd25hbWVzKEFQKSkgJT4lCiAgYWRkX3RyYWNlKHg9d2lyZWxlc3MkeFt0b3BfdGhyZWVdLCB5PXdpcmVsZXNzJHlbdG9wX3RocmVlXSwgCiAgICAgICAgICAgIG5hbWUgPSAibmVpZ2hib3JzIiwgbW9kZT0ibWFya2VycyIsIAogICAgICAgICAgICB0ZXh0PXBhc3RlKHRvcF90aHJlZSwgIjxicj4iLCBob3Zlcl90ZXh0W3RvcF90aHJlZV0pKSAlPiUKICBhZGRfdHJhY2UoeD13aXJlbGVzcyR4W3BvaW50X2luZGV4XSwgeT13aXJlbGVzcyR5W3BvaW50X2luZGV4XSwgCiAgICAgICAgICAgIG5hbWUgPSAicG9pbnQiLCBtb2RlPSJtYXJrZXJzIiwgCiAgICAgICAgICAgIHRleHQ9cGFzdGUocG9pbnRfaW5kZXgsICI8YnI+IiwgaG92ZXJfdGV4dFtwb2ludF9pbmRleF0pKQogIApwCmBgYAoKCmBgYHtyfQprbm5fcHJlZGljdGlvbnNbM10KYGBgCgpgYGB7cn0KbmVpZ2hib3JzID0gbnVtZXJpYyg1KQpmb3IgKGkgaW4gMTo1KSB7CiAgbmVpZ2hib3JzW2ldID0gKDE6bilbLXBvaW50X2luZGV4XVt3aGljaC5taW4oYXMubWF0cml4KHBkaXN0OjpwZGlzdCgxL3dpcmVsZXNzWywoMzo3KVstaV1dW3BvaW50X2luZGV4LF0sIDEvd2lyZWxlc3NbLCgzOjcpWy1pXV1bLXBvaW50X2luZGV4LF0pKSldCn0KbmVpZ2hib3JzCmBgYAoKCmFmdGVyIGFuYWx5emluZyB0aGUgZXJyb3IsIEkgZmluZCB0aGF0IHBvaW50IDI0MydzIHNpZ25hbCBmb3IgQVAzIGlzIGNvbXBsZXRlbHkgYmFkCmNvbXBhcmluZyB0byBpdHMgbmVpZ2hib3JzLCBsZXRzIGNoZWNrIG90aGVyIGFjY2VzcyBwb2ludHMuCgpTb21lIHBvaW50cywgdGhleSBvbmx5IGYqIHVwIG9uIHNpZ25hbCBmcm9tIGFuIGFjY2VzcyBwb2ludC4gCgpgYGB7cn0Kc2V0LnNlZWQoMTIzNDUpCnNhbXBsZV9pbmRpY2VzID0gc2FtcGxlKDE6MjU0LCA1MCkKcGxvdCh3aXJlbGVzcyR4W3NhbXBsZV9pbmRpY2VzXSwgd2lyZWxlc3MkeVtzYW1wbGVfaW5kaWNlc10sIHlsaW09YygwLCAxNDUpLCB4bGltPWMoMTAsIDIzNSkpCnBvaW50cyhrbm5feFtzYW1wbGVfaW5kaWNlc10sIGtubl95W3NhbXBsZV9pbmRpY2VzXSwgY29sPSJ5ZWxsb3ciKQpwb2ludHMoKGtubl94LWRmX3gpW3NhbXBsZV9pbmRpY2VzXSwgKGtubl95LWRmX3kpW3NhbXBsZV9pbmRpY2VzXSwgY29sPSJyZWQiKQoKc2VnbWVudHMod2lyZWxlc3MkeFtzYW1wbGVfaW5kaWNlc10sIHdpcmVsZXNzJHlbc2FtcGxlX2luZGljZXNdLCBrbm5feFtzYW1wbGVfaW5kaWNlc10sIGtubl95W3NhbXBsZV9pbmRpY2VzXSwgY29sPSJibHVlIikKc2VnbWVudHMoa25uX3hbc2FtcGxlX2luZGljZXNdLCBrbm5feVtzYW1wbGVfaW5kaWNlc10sIChrbm5feC1kZl94KVtzYW1wbGVfaW5kaWNlc10sIChrbm5feS1kZl95KVtzYW1wbGVfaW5kaWNlc10sIGNvbD0iZ3JlZW4iKQojc2VnbWVudHMod2lyZWxlc3MkeCwgd2lyZWxlc3MkeSwga25uX3grZGZfeCwga25uX3krIGRmX3ksIGNvbD0iZ3JlZW4iKQpgYGAKCgpgYGB7ciBzaWduYWxfYW5kX2Rpc3RhbmNlc30KcGFyKG1mcm93PWMoMSwyKSkKcGxvdCh3aXJlbGVzc19mZWF0dXJlJFMxLCB3aXJlbGVzc19mZWF0dXJlJGQxLCBtYWluPSJhcDEiKQpwbG90KHdpcmVsZXNzX2ZlYXR1cmUkUzIsIHdpcmVsZXNzX2ZlYXR1cmUkZDIsIG1haW49ImFwMiIpCnBsb3Qod2lyZWxlc3NfZmVhdHVyZSRTMywgd2lyZWxlc3NfZmVhdHVyZSRkMywgbWFpbj0iYXAzIikKcGxvdCh3aXJlbGVzc19mZWF0dXJlJFM0LCB3aXJlbGVzc19mZWF0dXJlJGQ0LCBtYWluPSJhcDQiKQpwbG90KHdpcmVsZXNzX2ZlYXR1cmUkUzUsIHdpcmVsZXNzX2ZlYXR1cmUkZDUsIG1haW49ImFwNSIpCiMgbWF5YmUgbm9ybWFsIG1ldGhvZCBmb3Igc2lnbmFsIDwgNzAKIyBtb2RlbGluZyBsb2cgZGlzdGFuY2Ugd2hlbiBzaWduYWwgPiA3MCAKCnBsb3QoeT13aXJlbGVzc19mZWF0dXJlJFMxLCB3aXJlbGVzc19mZWF0dXJlJGQxLCBtYWluPSJhcDEiKQpwbG90KHk9d2lyZWxlc3NfZmVhdHVyZSRTMiwgd2lyZWxlc3NfZmVhdHVyZSRkMiwgbWFpbj0iYXAyIikKcGxvdCh5PXdpcmVsZXNzX2ZlYXR1cmUkUzMsIHdpcmVsZXNzX2ZlYXR1cmUkZDMsIG1haW49ImFwMyIpCnBsb3QoeT13aXJlbGVzc19mZWF0dXJlJFM0LCB3aXJlbGVzc19mZWF0dXJlJGQ0LCBtYWluPSJhcDQiKQpwbG90KHk9d2lyZWxlc3NfZmVhdHVyZSRTNSwgd2lyZWxlc3NfZmVhdHVyZSRkNSwgbWFpbj0iYXA1IikKYGBgCgpGcm9tIHRoZSBmaXJzdCBoYWxmIG9mIHRoZSBncmFwaCwgd2UgY2FuIHNlZSBmb3IgZGlmZmVyZW50IEFQLAp0aGUgdmFyaWFuY2Ugc3Bpa2VzIGF0IGRpZmZlcmVudCBwb2ludHMuIAoKTG9va2luZyBhdCB0aGUgc2Vjb25kIGhhbGYgb2YgdGhlIGdyYXBocy4gIApGb3IgYWNjZXNzIHBvaW50IDUsIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBkaXN0YW5jZSBhbmQgc2lnbmFsIGlzIHZlcnkgd2Vhaywgd2hpbGUgIApvdGhlcnMgYXJlIG1vcmUgc3RhYmxlLiBUaGlzIG1heSBoYXZlIHRvIGRvIHdpdGggQVA1IGlzIGluIHRoZSBjZW50ZXIgb2YgdGhlIGJ1aWxkaW5nLiAKCgpgYGB7ciBwbG90bHl9CmhvdmVyX3RleHQgPSBhcHBseSh3aXJlbGVzc19mZWF0dXJlLDEsIGZ1bmN0aW9uKHgpIHBhc3RlKHgsY29sbGFwc2UgPSAifCIpKQpwID0gcGxvdF9seSh3aXJlbGVzcywgeD1+eCwgeT1+eSwgbmFtZSA9ICJyZWNlaXZlcnMiLCB0eXBlPSJzY2F0dGVyIiwgCiAgICAgICAgICAgIG1vZGU9Im1hcmtlcnMiLCB0ZXh0PXBhc3RlKDE6MjU0LCAiPGJyPiIsIGhvdmVyX3RleHQpKSAlPiUgCiAgYWRkX3RyYWNlKHg9QVAkeCwgeT1BUCR5LCBuYW1lID0gIndpZmkgcG9zdCIsIG1vZGU9Im1hcmtlcnMiLCB0ZXh0PXJvd25hbWVzKEFQKSkKCnAKYGBgCgoKYGBge3J9CmtjbHVzdGVycyA9IGttZWFucyh3aXJlbGVzc1ssMzo3XSwgNSkKI2tjbHVzdGVycyRjbHVzdGVyCgpnZ3Bsb3QoZGF0YT13aXJlbGVzcykgKwogIGdlb21fcG9pbnQoYWVzKHg9eCx5PXkpLCBjb2xvdXI9a2NsdXN0ZXJzJGNsdXN0ZXIpIApgYGAKCgpgYGB7cn0KZ2dwbG90KGRhdGE9d2lyZWxlc3MpICsKICBnZW9tX3BvaW50KGFlcyh4PXgseT15KSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKGtjbHVzdGVycyRjbHVzdGVyKQoKYGBgCgoKYGBge3J9CmN1dG9mZiA9IDY4CiMgc2VlbXMgbGlrZSA3MCBpcyBhIGdvb2QgY3V0IG9mZiBsZXRzIGNoZWNrIGhvdyBtYW55IHBvaW50cyBoYXZlIG1vcmUgdGhhbiA3MAp3aXJlbGVzc19zdHJvbmcgPSB3aXJlbGVzcyAlPiUKICBtdXRhdGUoUzEgPSBTMSA+IC1jdXRvZmYpICU+JSAKICBtdXRhdGUoUzIgPSBTMiA+IC1jdXRvZmYpICU+JSAKICBtdXRhdGUoUzMgPSBTMyA+IC1jdXRvZmYpICU+JSAKICBtdXRhdGUoUzQgPSBTNCA+IC1jdXRvZmYpICU+JSAKICBtdXRhdGUoUzUgPSBTNSA+IC1jdXRvZmYpIAoKIyBzZWVtcyBsaWtlIDcwIGlzIG5vdCBhIGdvb2QgY3V0b2ZmIGFzIHdlIHRoaW5rCnRhYmxlKGFwcGx5KHdpcmVsZXNzX3N0cm9uZ1ssMzo3XSwgMSwgc3VtKSkKYGBgCgpgYGB7cn0KYmFkX2xvY2F0aW9ucyA9IHdpcmVsZXNzX3N0cm9uZ1thcy5udW1lcmljKGFwcGx5KHdpcmVsZXNzX3N0cm9uZ1ssMzo3XSwgMSwgc3VtKSkgPCAyLF0KCnBsb3QoeD1iYWRfbG9jYXRpb25zJHgsIHk9YmFkX2xvY2F0aW9ucyR5LCB5bGltPWMoMCwxNTApLCB4bGltPWMoMCwyMzApKQpwb2ludHMoeD1BUCR4LHk9QVAkeSwgY29sPSJyZWQiLCBjZXg9NSkKYGBgCgoKYGBge3J9ClZpZXcoZGF0YS5mcmFtZSh0YWJsZSh3aXJlbGVzcyR5KSkpCgpgYGAKCg==